/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.atomojo.app;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.sql.SQLException;
import java.text.ParseException;
import java.util.Date;
import java.util.Iterator;
import java.util.logging.Level;
import org.atomojo.app.db.DB;
import org.atomojo.app.db.DBIterator;
import org.atomojo.app.db.Entry;
import org.atomojo.app.db.Feed;
import org.infoset.xml.DocumentLoader;
import org.infoset.xml.Element;
import org.infoset.xml.InfosetFactory;
import org.infoset.xml.ItemConstructor;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.Name;
import org.infoset.xml.XMLException;
import org.infoset.xml.filter.RemoveDocumentFilter;
import org.infoset.xml.sax.SAXDocumentLoader;
import org.infoset.xml.util.WriterItemDestination;
import org.restlet.data.CharacterSet;
import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.data.Reference;
import org.restlet.data.Status;
import org.restlet.representation.OutputRepresentation;
import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;
import org.restlet.resource.ServerResource;

/**
 *
 * @author alex
 */
public class UpdatedResource extends ServerResource {

   public UpdatedResource() {
      setNegotiated(false);
   }

   public Representation get() {
      try {
         String path = getRequest().getResourceRef().getRemainingPart();
         int q = path.indexOf('?');
         if (q>=0) {
            path = path.substring(0,q);
         }
         int h = path.indexOf('#');
         if (h>=0) {
            path = path.substring(0,h);
         }
         final DB atomDB = (DB)getContext().getAttributes().get(AtomApplication.DB_ATTR);
         Feed feed = null;
         if (path.length()>0) {
            String [] segments = path.split("/");
            feed = atomDB.findFeedByPath(segments);
            if (feed==null) {
               getResponse().setStatus(Status.CLIENT_ERROR_NOT_FOUND);
               return new StringRepresentation("No feed found at path: "+path);
            }
         }
         Form form = getRequest().getResourceRef().getQueryAsForm();
         String sinceS = form.getFirstValue("since");
         Date since = null;
         if (sinceS!=null && sinceS.length()>0) {
            try {
               since = AtomResource.fromXSDDate(sinceS);
            } catch (ParseException ex) {
               getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
               return new StringRepresentation("Bad since value '"+sinceS+"': "+ex.getMessage());
            }
         }
         String limitS = form.getFirstValue("limit");
         int limit = 100;
         if (limitS!=null && limitS.length()>0) {
            try {
               limit = Integer.parseInt(limitS);
            } catch (NumberFormatException  ex) {
               getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
               return new StringRepresentation("Bad limit value '"+limitS+"': "+ex.getMessage());
            }
         }
         String startS = form.getFirstValue("start");
         int start = 1;
         if (startS!=null && startS.length()>0) {
            try {
               start = Integer.parseInt(startS);
            } catch (NumberFormatException  ex) {
               getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
               return new StringRepresentation("Bad start value '"+startS+"': "+ex.getMessage());
            }
         }
         final int startRow = start;
         final int limitCount = limit;
         final Date sinceMarker = since==null ? new Date() : since;
         final Feed target = feed;
         return new OutputRepresentation(MediaType.APPLICATION_ATOM) {
            public void write(OutputStream os)
               throws IOException
            {
               setCharacterSet(CharacterSet.UTF_8);
               try {
                  String prevRef = null;
                  if (startRow>1) {
                     prevRef = getRequest().getResourceRef().toString();
                     int q = prevRef.indexOf('?');
                     prevRef = prevRef.substring(0,q);
                     prevRef += "?";
                     if (sinceMarker!=null) {
                        prevRef += "since=";
                        prevRef += AtomResource.toXSDDate(sinceMarker);
                        prevRef += "&";
                     }
                     prevRef += "start=";
                     int prevStart = startRow-limitCount;
                     int prevLimit = limitCount;
                     if (prevStart<1) {
                        prevStart = 1;
                        prevLimit = startRow-1;
                     }
                     prevRef += prevStart;
                     prevRef += "&limit=";
                     prevRef += prevLimit;
                  }
                  String nextRef = getRequest().getResourceRef().toString();
                  int q = nextRef.indexOf('?');
                  if (q>=0) {
                     nextRef = nextRef.substring(0,q);
                  }
                  nextRef += "?";
                  if (sinceMarker!=null) {
                     nextRef += "since=";
                     nextRef += AtomResource.toXSDDate(sinceMarker);
                     nextRef += "&";
                  }
                  nextRef += "start=";
                  nextRef += (startRow+limitCount);
                  nextRef += "&limit=";
                  nextRef += limitCount;
                  DBIterator<Entry> entries = atomDB.getEntriesModifiedBefore(target,sinceMarker,startRow,limitCount);
                  Form form = getRequest().getResourceRef().getQueryAsForm();
                  generate(new WriterItemDestination(new OutputStreamWriter(os,"UTF-8"),"UTF-8"),entries,prevRef,nextRef);
                  entries.finished();
               } catch (XMLException ex) {
                  getContext().getLogger().log(Level.SEVERE,"Cannot serialize metadata feed.",ex);
                  throw new IOException("Cannot serialize metadata feed.");
               } catch (SQLException ex) {
                  getContext().getLogger().log(Level.SEVERE,"Cannot serialize metadata feed.",ex);
                  throw new IOException("Cannot serialize metadata feed.");
               }
            }
         };
      } catch (SQLException ex) {
         getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
         getLogger().log(Level.SEVERE,"Database error during updated resource generation.",ex);
         return new StringRepresentation("Cannot get feed from database due to error");
      }
   }

   static void link(ItemConstructor constructor,ItemDestination dest,String rel,String href)
      throws XMLException
   {
      Element linkE = constructor.createElement(AtomResource.LINK_NAME);
      linkE.setAttributeValue("rel",rel);
      linkE.setAttributeValue("href",href);
      linkE.setAttributeValue("type","application/atom+xml");
      dest.send(linkE);

      dest.send(constructor.createElementEnd(AtomResource.LINK_NAME));
   }

   static void text(ItemConstructor constructor,ItemDestination dest,Name name,String text)
      throws XMLException
   {
      dest.send(constructor.createElement(name));
      dest.send(constructor.createCharacters(text));
      dest.send(constructor.createElementEnd(name));
   }

   public void generate(ItemDestination dest,Iterator<Entry> entries,String prevRef,String nextRef)
      throws XMLException
   {
      //getContext().getLogger().info("Generating feed for term "+term);
      ItemConstructor constructor = InfosetFactory.getDefaultInfoset().createItemConstructor();
      dest.send(constructor.createDocument());
      dest.send(constructor.createElement(AtomResource.FEED_NAME));
      dest.send(constructor.createCharacters("\n"));
      link(constructor,dest,"self",getRequest().getResourceRef().toString());
      if (prevRef!=null) {
         link(constructor,dest,"previous",prevRef);
      }
      link(constructor,dest,"next",nextRef);
      dest.send(constructor.createCharacters("\n"));
      text(constructor,dest,AtomResource.TITLE_NAME,"Updated");
      dest.send(constructor.createCharacters("\n"));
      text(constructor,dest,AtomResource.ID_NAME,getRequest().getResourceRef().toString());
      dest.send(constructor.createCharacters("\n"));
      text(constructor,dest,AtomResource.UPDATED_NAME,AtomResource.toXSDDate(new Date()));
      Reference resourceBase = (Reference)getContext().getAttributes().get(AtomApplication.RESOURCE_BASE_ATTR);
      App app = (App)getContext().getAttributes().get(AtomApplication.APP_ATTR);
      try {
         while (entries.hasNext()) {
            Entry entry = entries.next();
            Feed feed = entry.getFeed();
            String feedPath = feed.getPath();
            String feedBaseURI = resourceBase.toString()+feedPath;
            dest.send(constructor.createCharacters("\n"));

            // get the entry representation
            Representation rep = app.getStorage().getEntry(feedBaseURI,feedPath,feed.getUUID(),entry.getUUID());

            // avoid thread creation because reading an output representation requires a thread
            StringWriter sw = new StringWriter();
            rep.write(sw);
            rep.release();
            DocumentLoader loader = new SAXDocumentLoader();
            loader.generate(new StringReader(sw.toString()), new RemoveDocumentFilter(dest));

         }
      } catch (Exception ex) {
         throw new XMLException("Exception while getting ancestors.",ex);
      }
      dest.send(constructor.createCharacters("\n"));
      dest.send(constructor.createElementEnd(AtomResource.FEED_NAME));
      dest.send(constructor.createDocumentEnd());
   }
}
